﻿using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Net;
using System.Threading;
using System.Xml;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Time;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;
using GHI.Premium.Net;
using Gadgeteer;

using GadgeteerLibrary;

namespace GadgeteerUseCaseOne
{
    public partial class Program
    {
        private GT.Timer temperatureCheckTimer;
        private GT.Timer sendTimer;
        private GT.Timer connectTimer;
        private GT.Timer heartBeatTimer;
        private string firstTime;
        private string lastTime;
        private RESTClient restClient;
        private ArrayList sensorData;
        private static bool timeSynchronized = false;
        private static bool isStoredBefore = false;

        private const byte PAY_LOAD_SIZE = 2;
        private const int CONNECT_INTERVAL = 300000;
        private const int HEARTBEAT_INTERVAL = 9000000;
        private const int MEASUREMENT_INTERVAL = 4000;
        private const int SEND_INTERVAL = 8000;
        private const short FAULT_TOLERANCE = 60;
        private const string TARGET_SSID = "Connectify-Zheng";
        private const string PASSWORD = "gadg3t33r";
        private const string SENSOR_NAME = "TemperatureLogger";

        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");
            this.InitializeSDCard();
            this.InitializeNetwork();
            this.InitializeSensor();
        }

        // Set up sd card event
        void InitializeSDCard()
        {
            sdCard.SDCardMounted += sdCard_SDCardMounted;
            sdCard.SDCardUnmounted += sdCard_SDCardUnmounted;
        }

        void sdCard_SDCardUnmounted(GTM.GHIElectronics.SDCard sender)
        {
            Debug.Print("The SD card has been unmounted");
            Debug.Print("DO NOT try to access it without mounting it again first");
        }

        void sdCard_SDCardMounted(GTM.GHIElectronics.SDCard sender, StorageDevice SDCard)
        {
            Debug.Print("SD card has been successfully mounted. You can now read/write/create/delete files");
            Debug.Print("Unmount before removing");
        }

        void InitializeNetwork()
        {
            while (!sdCard.IsCardMounted)
            {
                Debug.Print("Waiting for SD Card to be mounted");
            }

            this.Configure();

            connectTimer = new GT.Timer(CONNECT_INTERVAL);
            connectTimer.Tick += connectTimer_Tick;

            if (!wifi_RS21.Interface.IsOpen)
            {
                wifi_RS21.Interface.Open();
            }

            if (!wifi_RS21.Interface.NetworkInterface.IsDhcpEnabled)
            {
                wifi_RS21.Interface.NetworkInterface.EnableDhcp();
            }
            else
            {
                wifi_RS21.Interface.NetworkInterface.RenewDhcpLease();
            }

            //// Use the router's DHCP server to set my network info
            //wifi_RS21.UseDHCP();

            // Setup events
            wifi_RS21.NetworkDown += wifi_NetworkDown;
            wifi_RS21.NetworkUp += wifi_NetworkUp;
            wifi_RS21.Interface.NetworkAddressChanged += Interface_NetworkAddressChanged;
            wifi_RS21.Interface.WirelessConnectivityChanged += Interface_WirelessConnectivityChanged;

            NetworkInterfaceExtension.AssignNetworkingStackTo(wifi_RS21.Interface);

            try
            {
                if (this.WifiConnect())
                {
                    Thread.Sleep(2000);
                    Debug.Print("Up: " + wifi_RS21.IsNetworkUp.ToString());
                    Debug.Print("IP Address: " + wifi_RS21.Interface.NetworkInterface.IPAddress);
                    // After connecting, set local time, register the sensors and initiate the heart beat message
                    this.SetLocalTime();
                    this.RegisterSensor();
                    this.HeartBeatMessage();
                }
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        void Configure()
        {
            AppSettings appSettings = new AppSettings();

            using (FileStream xmlStream = sdCard.GetStorageDevice().OpenRead("testconfigfile"))
            {
                appSettings = ConfigurationManager.ReadConfiguration(xmlStream);
            }

            Debug.Print("User ID: " + appSettings.UserID);
            Debug.Print("Device ID: " + appSettings.DeviceID);
            Debug.Print("API Key: " + appSettings.APIKey);
            Debug.Print("Device Name: " + appSettings.DeviceName);

            Device device = new Device(appSettings.DeviceID, appSettings.DeviceName, appSettings.UserID);
            ArrayList sensors = new ArrayList();
            sensors.Add(new Sensor(
                SENSOR_NAME, SensorType.temperatureHumidity, "GHI", 
                MEASUREMENT_INTERVAL, SEND_INTERVAL, FAULT_TOLERANCE));
            restClient = new RESTClient(appSettings.APIKey, device, sensors);
            sensorData = new ArrayList();
            firstTime = lastTime = string.Empty;
        }

        void HeartBeatMessage()
        {
            heartBeatTimer = new GT.Timer(HEARTBEAT_INTERVAL);
            heartBeatTimer.Tick += heartBeatTimer_Tick;
            heartBeatTimer.Start();
        }

        void heartBeatTimer_Tick(GT.Timer timer)
        {
            if (wifi_RS21.IsNetworkConnected)
            {
                if (restClient.HeartBeatMessage())
                {
                    Debug.Print("Heart beat notification for Device "
                        + restClient.Device.Name + " successful");
                }
                else
                {
                    Debug.Print("Heart beat notification for Device "
                        + restClient.Device.Name + " failed");
                }
            }
            else
            {
                Debug.Print("Please check the network connection.");
            }
        }

        void connectTimer_Tick(GT.Timer timer)
        {
            if (this.WifiConnect())
            {
                Debug.Print("Reconnection successful");
            }
            else
            {
                Debug.Print("Reconnection failed");
            }
        }

        bool WifiConnect()
        {
            try
            {
                if (wifi_RS21.IsNetworkConnected)
                {
                    wifi_RS21.Interface.Disconnect();
                    Thread.Sleep(2000);
                }

                // Hold the network to connect to
                WiFiNetworkInfo[] targetNetwork = wifi_RS21.Interface.Scan(TARGET_SSID);

                // Connect to the specified network
                if (targetNetwork != null && targetNetwork.Length > 0)
                {
                    WiFiNetworkInfo target = targetNetwork[0];

                    Debug.Print("****" + target.SSID + "****");
                    Debug.Print("ChannelNumber = " + target.ChannelNumber);
                    Debug.Print("NetworkType = " + target.networkType);
                    Debug.Print("PhysicalAddress = " + HttpUtility.GetMACAddress(target.PhysicalAddress));
                    Debug.Print("RSSI = " + target.RSSI);
                    Debug.Print("SecMode = " + target.SecMode);

                    wifi_RS21.Interface.Join(target, PASSWORD);

                    Debug.Print("Network joined");
                    return true;
                }
                else
                {
                    Debug.Print(TARGET_SSID + " wireless network not found");
                    return false;
                }
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
                return false;
            }
        }

        void SetLocalTime()
        {
            TimeService.SystemTimeChanged += TimeService_SystemTimeChanged;
            TimeService.TimeSyncFailed += TimeService_TimeSyncFailed;
            TimeService.Settings = NtpClient.TimeServiceSetting();
            TimeService.Start();
        }

        void RegisterSensor()
        {
            while (!restClient.RegisterSensor())
            {
                Debug.Print("Registration failed");
            }

            foreach (Object o in restClient.Sensors)
            {
                Sensor s = (Sensor)o;
                Debug.Print("Sensor ID for " + s.Name + ": " + s.Id);
            }
            Debug.Print("Registration successful");
        }

        void Interface_WirelessConnectivityChanged(object sender, WiFiRS9110.WirelessConnectivityEventArgs e)
        {
            if (e.IsConnected)
            {
                Debug.Print("Network is connected");
                if (connectTimer.IsRunning)
                {
                    connectTimer.Stop();
                }
            }
            else
            {
                Debug.Print("Network is disconnected");
                if (!connectTimer.IsRunning)
                {
                    connectTimer.Start();
                }
            }
        }

        void TimeService_TimeSyncFailed(object sender, TimeSyncFailedEventArgs e)
        {
            Debug.Print("Error synchronizing system time with NTP server: " + e.ErrorCode);
        }

        void TimeService_SystemTimeChanged(object sender, SystemTimeChangedEventArgs e)
        {
            Debug.Print("Network time received: " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
            if (!timeSynchronized)
            {
                timeSynchronized = true;
            }
        }

        // handle the network changed events
        void wifi_NetworkDown(GTM.Module.NetworkModule sender,
            GT.Modules.Module.NetworkModule.NetworkState state)
        {
            if (state == GTM.Module.NetworkModule.NetworkState.Down)
            {
                Debug.Print("Network Down event; state = Down");
            }
            else
            {
                Debug.Print("Network Down event; state = Up");
            }
        }

        void wifi_NetworkUp(GTM.Module.NetworkModule sender,
            GT.Modules.Module.NetworkModule.NetworkState state)
        {
            if (state == GTM.Module.NetworkModule.NetworkState.Up)
            {
                Debug.Print("Network Up event; state = Up");
            }
            else
            {
                Debug.Print("Network Up event; state = Down");
            }
        }

        void Interface_NetworkAddressChanged(object sender, EventArgs e)
        {
            if (wifi_RS21.IsNetworkConnected)
            {
                if (restClient.NotifyIPAddress(wifi_RS21.Interface.NetworkInterface.IPAddress))
                {
                    Debug.Print("Notification of IP Address changing for Device "
                        + restClient.Device.Name + " successful");
                }
                else
                {
                    Debug.Print("Notification of IP Address changing for Device "
                        + restClient.Device.Name + " failed");
                }
            }
            else
            {
                Debug.Print("Please check the network connection.");
            }
        }

        // Set up senor event and initialize the timer
        void InitializeSensor()
        {
            this.temperatureHumidity.MeasurementComplete += this.TemperatureHumidityMeasurementComplete;
            this.temperatureCheckTimer = new GT.Timer(MEASUREMENT_INTERVAL);
            this.temperatureCheckTimer.Tick += temperatureCheckTimer_Tick;
            this.temperatureCheckTimer.Start();
            this.sendTimer = new GT.Timer(SEND_INTERVAL);
            this.sendTimer.Tick += sendTimer_Tick;
            this.sendTimer.Start();
        }

        void temperatureCheckTimer_Tick(GT.Timer timer)
        {
            this.temperatureHumidity.RequestMeasurement();
        }

        // Once the measurement is complete, record it
        void TemperatureHumidityMeasurementComplete(GTM.Seeed.TemperatureHumidity sender,
            double temperature, double relativeHumidity)
        {
            Debug.Print("Temperature: " + temperature +
                ", Relative Humidity: " + relativeHumidity);

            TimedData tempData = new TimedData(
                DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"),
                temperature.ToString(), relativeHumidity.ToString());

            sensorData.Add(tempData);

            if (sdCard.IsCardInserted && sensorData.Count >= PAY_LOAD_SIZE)
            {
                if (!sdCard.IsCardMounted)
                {
                    sdCard.MountSDCard();
                }

                // Record the time span of the historic data
                if (firstTime.Equals(string.Empty))
                {
                    firstTime = ((TimedData)sensorData[0]).DataTime;
                }
                lastTime = ((TimedData)sensorData[sensorData.Count - 1]).DataTime;

                string filePath = sdCard.GetStorageDevice().RootDirectory + @"\data.log";
                if (!isStoredBefore && IsExisting("data.log"))
                {
                    restClient.DeleteFile(filePath);
                }

                // Record the sensor data into "data.log" in Micro SD Card
                restClient.AppendFile(filePath, sensorData);
                sensorData.Clear();

                if (!isStoredBefore)
                {
                    isStoredBefore = true;
                }
            }
        }

        // Once the sending timer event is raised, send the data
        void sendTimer_Tick(GT.Timer timer)
        {
            try
            {
                if (timer.IsRunning)
                {
                    timer.Stop();
                }
                Debug.Print("Send data here");

                if (wifi_RS21.IsNetworkConnected)
                {
                    if (IsExisting("data.log"))
                    {
                        string rootDirectory = sdCard.GetStorageDevice().RootDirectory;

                        if (isStoredBefore)
                        {
                            if (restClient.SendHistoricData(rootDirectory + @"\data.log",
                                firstTime, lastTime, SENSOR_NAME))
                            {
                                restClient.DeleteFile(rootDirectory + @"\data.log");
                                firstTime = lastTime = string.Empty;
                                Debug.Print("historic data sending successful");
                            }
                            else
                            {
                                Debug.Print("historic data sending failed");
                            }
                        }
                        else
                        {
                            restClient.DeleteFile(rootDirectory + @"\data.log");
                        }
                    }
                    else if (sensorData.Count >= PAY_LOAD_SIZE)
                    {
                        if (restClient.SendCurrentData(sensorData,
                            ((TimedData)sensorData[0]).DataTime,
                            ((TimedData)sensorData[sensorData.Count - 1]).DataTime,
                            SENSOR_NAME))
                        {
                            sensorData.Clear();
                            Debug.Print("current data sending successful");
                        }
                        else
                        {
                            Debug.Print("current data sending failed");
                        }
                    }
                }
                else
                {
                    Debug.Print("Please check the network connection.");
                }
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);

                if (this.WifiConnect())
                {
                    Debug.Print("Reconnection successful");
                }
                else
                {
                    Debug.Print("Reconnection failed");
                }
            }
            finally
            {
                if (!timer.IsRunning)
                {
                    timer.Start();
                }
            }
        }

        bool IsExisting(string fileName)
        {
            if (sdCard.IsCardInserted)
            {
                if (!sdCard.IsCardMounted)
                {
                    sdCard.MountSDCard();
                }

                foreach (string s in sdCard.GetStorageDevice().ListRootDirectoryFiles())
                {
                    if (s.Equals(fileName))
                    {
                        return true;
                    }
                }
                return false;
            }
            else
            {
                return false;
            }
        }
    }
}